package test.demo;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.NoArgsConstructor;
import tech.waterism.modelbase.PredictBase;
import tech.waterism.modelbase.PredictResult;

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

// 降雨径流模型HMCE.dll
@NoArgsConstructor
public class HMCE extends PredictBase {
    List<TimeSeriesData> seriesData;
    private ModelParameters params = new ModelParameters();

    public HMCE(Map<String, String> data) {
        super();
        // 读取参数
        // 大写
//        params.b = Double.parseDouble((data.get("B")));
//        params.ek = Double.parseDouble((data.get("EK")));
//        params.fc = Double.parseDouble((data.get("FC")));
//        params.imp = Double.parseDouble((data.get("IMP")));
//        params.kg = Double.parseDouble((data.get("KG")));
//        params.ku = Double.parseDouble((data.get("KU")));
//        params.tg = Double.parseDouble((data.get("TG")));
//        params.tu = Double.parseDouble((data.get("TU")));
//        params.wgm = Double.parseDouble((data.get("WGM")));
//        params.wum = Double.parseDouble((data.get("WUM")));
//        params.kbase = Double.parseDouble((data.get("KBASE")));
//        params.area = Double.parseDouble((data.get("AREA")));
//        params.timestep = Double.parseDouble((data.get("clen")));
        // 小写
        params.b = Double.parseDouble((data.get("B")));
        params.ek = Double.parseDouble((data.get("Ek")));
        params.fc = Double.parseDouble((data.get("fc")));
        params.imp = Double.parseDouble((data.get("IMP")));
        params.kg = Double.parseDouble((data.get("Kg")));
        params.ku = Double.parseDouble((data.get("Ku")));
        params.tg = Double.parseDouble((data.get("Tg")));
        params.tu = Double.parseDouble((data.get("Tu")));
        params.wgm = Double.parseDouble((data.get("Wgm")));
        params.wum = Double.parseDouble((data.get("Wum")));
        params.kbase = Double.parseDouble((data.get("Kbase")));
        params.area = Double.parseDouble((data.get("Area")));
        params.timestep = Double.parseDouble((data.get("clen")));

        // 计算 wgm
        params.wgm *= params.wum;  // 参数给出的是比率

        // 转换 imp
        params.imp /= 100;

        String[] floodTm = JSON.parseObject(data.get("dt"), String[].class);// input[i].data.dt
        double[] floodDrp = JSON.parseObject(data.get("rain"), double[].class);// input[i].data.rain

        seriesData = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        for (int i = 0; i < floodTm.length; i++) {
            seriesData.add(new TimeSeriesData(LocalDateTime.parse(floodTm[i], formatter), floodDrp[i]));
        }

        // 添加第一个数据点,时间为开始时间,降水量为 0
        LocalDateTime firstTime = seriesData.get(0).timeValue.minusHours((long) params.timestep);
        seriesData.add(0, new TimeSeriesData(firstTime, 0.0));
    }

    // 保留指定小数点位数
    public static double round(double value, int places) {
        long factor = (long) Math.pow(10, places);
        value = value * factor;
        long tmp = Math.round(value);
        return (double) tmp / factor;
    }

    public static void main(String[] args) throws IOException {

        // a. 模型参数 和 面平均降水数据
        //String a = "{\"B\":\"0.114\",\"EK\":\"0.797\",\"FC\":\"7.377\",\"IMP\":\"1.0\",\"KG\":\"0.53\",\"KU\":\"0.423\",\"TG\":\"4.703\",\"TU\":\"3.0\",\"WGM\":\"3.008\",\"WUM\":\"4.217\",\"KBASE\":\"0.002\",\"AREA\":\"11207.0\",\"clen\":\"1\",\"dt\":\"[\\\"2024-03-23 08:00:00\\\",\\\"2024-03-23 09:00:00\\\",\\\"2024-03-23 10:00:00\\\",\\\"2024-03-23 11:00:00\\\",\\\"2024-03-23 12:00:00\\\",\\\"2024-03-23 13:00:00\\\",\\\"2024-03-23 14:00:00\\\",\\\"2024-03-23 15:00:00\\\",\\\"2024-03-23 16:00:00\\\",\\\"2024-03-23 17:00:00\\\",\\\"2024-03-23 18:00:00\\\",\\\"2024-03-23 19:00:00\\\",\\\"2024-03-23 20:00:00\\\",\\\"2024-03-23 21:00:00\\\",\\\"2024-03-23 22:00:00\\\",\\\"2024-03-23 23:00:00\\\",\\\"2024-03-24 00:00:00\\\"]\",\"rain\":\"[10.0,0.0,0.2,0.8,2.0,5.0,8.0,8.0,8.0,2.0,0.2,0.0,0.0,0.0,0.0,0.0,0.0]\"}";
        String a = "{\"B\":\"0.114\",\"Ek\":\"0.797\",\"fc\":\"7.377\",\"IMP\":\"1.0\",\"Kg\":\"0.53\",\"Ku\":\"0.423\",\"Tg\":\"4.703\",\"Tu\":\"3.0\",\"Wgm\":\"3.008\",\"Wum\":\"4.217\",\"Kbase\":\"0.002\",\"Area\":\"11207.0\",\"clen\":\"1\",\"dt\":\"[\\\"2024-03-23 08:00:00\\\",\\\"2024-03-23 09:00:00\\\",\\\"2024-03-23 10:00:00\\\",\\\"2024-03-23 11:00:00\\\",\\\"2024-03-23 12:00:00\\\",\\\"2024-03-23 13:00:00\\\",\\\"2024-03-23 14:00:00\\\",\\\"2024-03-23 15:00:00\\\",\\\"2024-03-23 16:00:00\\\",\\\"2024-03-23 17:00:00\\\",\\\"2024-03-23 18:00:00\\\",\\\"2024-03-23 19:00:00\\\",\\\"2024-03-23 20:00:00\\\",\\\"2024-03-23 21:00:00\\\",\\\"2024-03-23 22:00:00\\\",\\\"2024-03-23 23:00:00\\\",\\\"2024-03-24 00:00:00\\\"]\",\"rain\":\"[10.0,0.0,0.2,0.8,2.0,5.0,8.0,8.0,8.0,2.0,0.2,0.0,0.0,0.0,0.0,0.0,0.0]\"}";
        System.out.println(a);
        Map data = JSON.parseObject(a, Map.class);
        HMCE model = new HMCE(data);
        //List<TimeSeriesData> seriesData = model.seriesData;
        PredictResult result = model.predict();
        System.out.println(result);

//        // b.
//        String FILE_DIRECTORY = "C:\\NFFS\\Temp\\";
//        String filePath = FILE_DIRECTORY + "2020300011.tem";
//        HMCE model = new HMCE();
//
//        // 读取输入文件
//        String[] inputFiles = model.readInputFile(filePath);
//        String paramFile = inputFiles[0];
//        String precipitationFile = inputFiles[5];
//
//        // b.读取模型参数
//        //ModelParameters params = readParameters(paramFile);
//        model.params = model.readParameters(paramFile);
//
//        // b.读取面平均降水数据
//        List<TimeSeriesData> seriesData = model.readPrecipitationData(precipitationFile);
//        System.out.println(seriesData);
//
//        // 模型计算
//        model.runModel(seriesData);
//
//        // 输出结果
//        model.writeOutputFile(seriesData, inputFiles[8]);
//        model.writeOutputFile(seriesData, inputFiles[3]);
//
//        // 写入日志文件
//        model.writeLogFile(FILE_DIRECTORY + "HMCE_Log.txt", paramFile, precipitationFile);

    }

    @Override
    public void init() {

    }

    @Override
    public PredictResult predict() {

        PredictResult result = new PredictResult();

        // 计算产流
        List<TimeSeriesData> runoffData = calculateRunoff(seriesData);
        double[][] runoffArray = Stream.of(
                        runoffData.stream()
                                .mapToDouble(data -> data.saturationRunoff)
                                .skip(1) // 跳过第一个元素
                                .toArray(),
                        runoffData.stream()
                                .mapToDouble(data -> data.groundwaterRunoff)
                                .skip(1) // 跳过第一个元素
                                .toArray()
                )
                .toArray(double[][]::new);
        double[] runoffSim = runoffData.stream()
                .mapToDouble(data -> data.saturationRunoff + data.groundwaterRunoff)
                .skip(1) // 忽略第一个元素
                .toArray();

//        double[][] RSim = new double[3][];
//        Map<String, double[]> map = new HashMap<>();
//        //流域地表产流
//        RSim[0] = map.get("RS");
//        //流域土壤产流
//        RSim[1] = map.get("RI");
//        //流域地下产流
//        RSim[2] = map.get("RG");

        //流域面积总产流，流域地表产流 RS[j] +  流域土壤产流RI[j] + 流域地下产流RG[j];
        //result.setRunoffSim(map.get("runoffSim"));
        //result.setRSim(RSim);
        result.setRunoffSim(runoffSim);
        result.setRSim(runoffArray);


        // 计算汇流
        calculateFlow(seriesData);
        // 统计总流量
        calculateTotalFlow(seriesData);

        double[] totalFlowArray = seriesData.stream()
                .mapToDouble(data -> data.totalFlow)
                .skip(1) // 忽略第一个元素
                .toArray();

        //河道演进结果
        //result.setQSim(map.get("QSim"));
        result.setQSim(totalFlowArray);


        // 更新状态
        List<String> WUP = new ArrayList<>();
        List<String> WLP = new ArrayList<>();
        List<String> WDP = new ArrayList<>();
        List<String> SP = new ArrayList<>();
        List<String> FRP = new ArrayList<>();
        List<String> QXSIG = new ArrayList<>();
        List<String> QSIG = new ArrayList<>();
        List<String> QSP = new ArrayList<>();
        List<String> QIP = new ArrayList<>();
        List<String> QGP = new ArrayList<>();

        Map<String, List<String>> status = new HashMap<>();
        //上层张力水含量
        status.put("WUP", WUP);
        //下层张力水含量
        status.put("WLP", WLP);
        //深层张力水含量
        status.put("WDP", WDP);
        //产流面积自由水深
        status.put("SP", SP);
        //产流面积系数
        status.put("FRP", FRP);
        //河段下断面各时段初始流量
        status.put("QXSIG", QXSIG);
        //各时段初始总基流
        status.put("QSIG", QSIG);
        //地表坡面汇流
        status.put("QSP", QSP);
        //坡面汇流-壤中流
        status.put("QIP", QIP);
        //地下坡面汇流
        status.put("QGP", QGP);
        result.setNewStatus(status);


        return result;
    }

    private void runModel(List<TimeSeriesData> seriesData) {

        // 计算产流
        calculateRunoff(seriesData);

        // 计算汇流
        calculateFlow(seriesData);

        // 统计总流量
        calculateTotalFlow(seriesData);

    }

    // 读取输入文件
    private String[] readInputFile(String filePath) throws IOException {
        String[] inputFiles = new String[10];

        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            // 读取每一行,获取文件路径
            for (int i = 0; i < 10; i++) {
                String line = reader.readLine();
                if (line != null) {
                    inputFiles[i] = line.trim();
                }
            }
        }

        return inputFiles;
    }

    // 读取模型参数
    private ModelParameters readParameters(String paramFile) throws IOException {
        //ModelParameters params = new ModelParameters();

        try (BufferedReader reader = new BufferedReader(new FileReader(paramFile))) {
            // 读取 STCD
            //params.stcd =
            reader.readLine().trim();

            // 跳过两行
            // PARAMETER
            // &PARA_TABLE
            reader.readLine();
            reader.readLine();

            // 读取其他参数
            params.b = Double.parseDouble(readParameterValue(reader.readLine()));
            params.ek = Double.parseDouble(readParameterValue(reader.readLine()));
            params.fc = Double.parseDouble(readParameterValue(reader.readLine()));
            params.imp = Double.parseDouble(readParameterValue(reader.readLine()));
            params.kg = Double.parseDouble(readParameterValue(reader.readLine()));
            params.ku = Double.parseDouble(readParameterValue(reader.readLine()));
            params.tg = Double.parseDouble(readParameterValue(reader.readLine()));
            params.tu = Double.parseDouble(readParameterValue(reader.readLine()));
            params.wgm = Double.parseDouble(readParameterValue(reader.readLine()));
            params.wum = Double.parseDouble(readParameterValue(reader.readLine()));
            params.kbase = Double.parseDouble(readParameterValue(reader.readLine()));
            params.area = Double.parseDouble(readParameterValue(reader.readLine()));

            // 计算 wgm
            params.wgm *= params.wum;  // 参数给出的是比率

            // 转换 imp
            params.imp /= 100;
        }


        // 将 params 对象转换为 JSON 对象
        JSONObject jsonParams = new JSONObject();
        //jsonParams.put("stcd", params.stcd);
        jsonParams.put("b", params.b);
        jsonParams.put("ek", params.ek);
        jsonParams.put("fc", params.fc);
        jsonParams.put("imp", params.imp);
        jsonParams.put("kg", params.kg);
        jsonParams.put("ku", params.ku);
        jsonParams.put("tg", params.tg);
        jsonParams.put("tu", params.tu);
        jsonParams.put("wgm", params.wgm);
        jsonParams.put("wum", params.wum);
        jsonParams.put("kbase", params.kbase);
        jsonParams.put("area", params.area);

        // 打印 JSON 格式
        System.out.println(jsonParams.toString());

        return params;
    }

    private String readParameterValue(String line) {
        int equalIndex = line.indexOf('=');
        return line.substring(equalIndex + 1).trim();
    }

    // 读取面平均降水数据
    private List<TimeSeriesData> readPrecipitationData(String precipitationFile) throws IOException {
        List<TimeSeriesData> precipitationData = new ArrayList<>();

        // IYb、IMb、IDb、IHb分别表示开始时间的年、月、日和小时。
        // IYe、IMe、IDe、IHe分别表示结束时间的年、月、日和小时。
        // Num表示数据点的总数。
        // Step表示时间步长(小时)。
        // 接下来的Num行,每行包含一个时间点(IY、IM、ID、IH)和对应的降水量(ssP)。

        try (BufferedReader reader = new BufferedReader(new FileReader(precipitationFile))) {
            // 读取第一行,获取开始时间、结束时间、数据点总数和时间步长
            String[] tokens = reader.readLine().trim().split("\\s+");
            int iyb = Integer.parseInt(tokens[0]);
            int imb = Integer.parseInt(tokens[1]);
            int idb = Integer.parseInt(tokens[2]);
            int ihb = Integer.parseInt(tokens[3]);
            int iye = Integer.parseInt(tokens[4]);
            int ime = Integer.parseInt(tokens[5]);
            int ide = Integer.parseInt(tokens[6]);
            int ihe = Integer.parseInt(tokens[7]);
            int num = Integer.parseInt(tokens[8]);
            int timestep = Integer.parseInt(tokens[9]);
            params.timestep = timestep;  // 读取为全局变量

            // 计算开始时间和结束时间
            LocalDateTime startTime = LocalDateTime.of(iyb, imb, idb, ihb, 0);
            LocalDateTime endTime = LocalDateTime.of(iye, ime, ide, ihe, 0);

            // 添加第一个数据点,时间为开始时间,降水量为 0
            TimeSeriesData initialData = new TimeSeriesData(startTime, 0.0);
            precipitationData.add(initialData);

            // 读取数据行
            LocalDateTime currentTime = startTime.plusHours(timestep); // 第一行为第一个时段值
            while (currentTime.isBefore(endTime) || currentTime.isEqual(endTime)) {
                String line = reader.readLine();
                if (line == null) {
                    break; // 如果读取到文件结尾,跳出循环
                }
                if (StrUtil.isBlank(line)) {
                    continue; // 空行跳过
                }
                line = line.trim();
                tokens = line.split("\\s+");
                int iy = Integer.parseInt(tokens[0]);
                int im = Integer.parseInt(tokens[1]);
                int id = Integer.parseInt(tokens[2]);
                int ih = Integer.parseInt(tokens[3]);
                double precipitation = Double.parseDouble(tokens[4]);

                LocalDateTime timeValue = LocalDateTime.of(iy, im, id, ih, 0);
                if (timeValue.isEqual(currentTime)) {
                    TimeSeriesData data = new TimeSeriesData(timeValue, precipitation);
                    precipitationData.add(data);
                    currentTime = currentTime.plusHours(timestep); // 根据时间步长移动到下一个时间点
                }
            }
        }

        return precipitationData;
    }

    // 计算产流
    private List<TimeSeriesData> calculateRunoff(List<TimeSeriesData> seriesData) {
        double wu = params.wum / 2;
        double wg = params.wgm / 2;
        double wm = params.wum + params.wgm;

        for (TimeSeriesData data : seriesData) {
            double pe = data.precipitation - params.ek;
            data.evaporation = (params.ek);

            if (pe > 0) {
                // 直接径流
                double rdic = pe * params.imp;
                pe -= rdic;
                data.directRunoff = (rdic);

                // 蓄满产流
                double w = wu + wg;
                double wmm = wm * (1 + params.b);
                double tema = 1 - w / wm;
                double temb = 1 / (1 + params.b);
                double a = wmm * (1 - Math.pow(tema, temb));

                if ((pe + a) >= wmm) {
                    data.saturationRunoff = (pe - (wm - w));
                } else {
                    tema = 1 - (pe + a) / wmm;
                    temb = wm * Math.pow(tema, (1 + params.b));
                    data.saturationRunoff = (pe - wm + w + temb);
                }

                // 下渗的水
                pe -= data.saturationRunoff;
                wu += pe;
                if (wu > params.wum) {
                    pe = params.wum - wu;
                    wu = 0;
                    wg += pe;
                    if (wg > params.wgm) {
                        wg = params.wgm;
                    }
                }

                // 分水源
                if (data.saturationRunoff > params.fc) {
                    data.groundwaterRunoff = (params.fc);
                    data.saturationRunoff = (data.saturationRunoff - params.fc);
                } else {
                    data.groundwaterRunoff = (data.saturationRunoff);
                    data.saturationRunoff = (0);
                }
                data.saturationRunoff = (data.saturationRunoff + rdic);  // 直接径流
            } else {
                // 两层蒸发
                wu += pe;  // PE是负值
                if (wu < 0) {
                    pe = wu * wg / params.wgm;
                    wu = 0;
                    wg += pe;
                    if (wg < 0) {
                        wg = 0;
                    }
                }
            }
        }

        return seriesData;
    }

    // 计算汇流
    private List<TimeSeriesData> calculateFlow(List<TimeSeriesData> seriesData) {
        // 计算慢速汇流
        double a = 0;
        for (TimeSeriesData data : seriesData) {
            a += data.groundwaterRunoff;
            int j = seriesData.indexOf(data) + (int) params.tg;  // TODO:tg是double，这里是否应该转int
            if (j < seriesData.size() - 1) {
                seriesData.get(j).totalFlow = a * params.kg;
            }
            a *= (1 - params.kg);
        }

        // 计算快速汇流
        for (TimeSeriesData data : seriesData) {
            data.saturationRunoff += data.totalFlow;
            data.totalFlow = 0;
        }

        a = 0;
        for (TimeSeriesData data : seriesData) {
            a += data.saturationRunoff;
            int j = seriesData.indexOf(data) + (int) params.tu;  // TODO:tg是double，这里是否应该转int
            if (j < seriesData.size() - 1) {
                seriesData.get(j).totalFlow = a * params.ku;
            }
            a *= (1 - params.ku);
        }

        return seriesData;
    }

    // 统计总流量
    private List<TimeSeriesData> calculateTotalFlow(List<TimeSeriesData> seriesData) {
        double a = params.kbase * params.area; // 每km2的基流量
        double conversionFactor = params.area * 10 / 36 / params.timestep; // 妙立米换算系数

        for (TimeSeriesData data : seriesData) {
            double totalFlow = a + data.totalFlow * conversionFactor;
            data.totalFlow = (totalFlow < 0 ? 0 : totalFlow);
        }

        return seriesData;
    }

    // 输出结果
    private void writeOutputFile(List<TimeSeriesData> data, String outputFile) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
            // 写入第一行: IYb、IMb、IDb、IHb、IYe、IMe、IDe、IHe、Num、Step
            TimeSeriesData firstData = data.get(0);
            TimeSeriesData lastData = data.get(data.size() - 1);
            int iyb = firstData.timeValue.getYear();
            int imb = firstData.timeValue.getMonthValue();
            int idb = firstData.timeValue.getDayOfMonth();
            int ihb = firstData.timeValue.getHour();
            int iye = lastData.timeValue.getYear();
            int ime = lastData.timeValue.getMonthValue();
            int ide = lastData.timeValue.getDayOfMonth();
            int ihe = lastData.timeValue.getHour();
            int num = data.size();
            int step = (int) (data.get(1).timeValue.until(data.get(2).timeValue, ChronoUnit.HOURS));

            writer.write(String.format("%04d %02d %02d %02d %04d %02d %02d %02d %06d %04d", iyb, imb, idb, ihb, iye, ime, ide, ihe, num, step));
            writer.newLine();

            // 写入数据行
            for (TimeSeriesData datum : data) {
                LocalDateTime timeValue = datum.timeValue;
                int iy = timeValue.getYear();
                int im = timeValue.getMonthValue();
                int id = timeValue.getDayOfMonth();
                int ih = timeValue.getHour();
                double totalFlow = datum.totalFlow;

                writer.write(String.format("%04d %02d %02d %02d %9.2f %9.2f", iy, im, id, ih, 0.0, totalFlow));
                writer.newLine();
            }
        }
    }

    // 写入日志文件
    private void writeLogFile(String filePath, String paramFile, String precipitationFile) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("HMCE Model Log");
            writer.newLine();
            writer.newLine();

            writer.write("Input File: " + filePath);
            writer.newLine();

            writer.write("Parameter File: " + paramFile);
            writer.newLine();

            writer.write("Precipitation Data File: " + precipitationFile);
            writer.newLine();

            writer.write("Execution Time: " + LocalDateTime.now().toString());
            writer.newLine();
        }
    }

    //@Data
    private class ModelParameters {
        //String stcd;

        /**
         * 蓄水容量曲线指数
         */
        double b;
        /**
         * 蒸发修正系数，当没有蒸发站点时表示时段平均蒸发量，单位mm
         */
        double ek;
        /**
         * 时段平均下渗率
         */
        double fc;
        /**
         * 不透水比例，单位%
         */
        double imp;
        /**
         * 慢速径流出流系数
         */
        double kg;
        /**
         * 快速径流出流系数
         */
        double ku;
        /**
         * 慢速径流滞时，单位是h
         */
        double tg;
        /**
         * 快速径流滞时，单位是h
         */
        double tu;
        /**
         * 下层土壤蓄水容量，单位mm
         */
        double wgm;
        /**
         * 表层土壤蓄水容量，单位mm
         */
        double wum;
        /**
         * 基流率，单位mm/km²
         */
        double kbase;
        /**
         * 面积，单位km²
         */
        double area;
        /**
         * 降水时段步长
         */
        double timestep;
    }

    //@Data
    private class TimeSeriesData {
        LocalDateTime timeValue;
        double precipitation;
        // 蒸发量
        double evaporation;
        // 直接径流量
        double directRunoff;
        // 蓄满产流量
        double saturationRunoff;
        // 地下水产流量
        double groundwaterRunoff;
        private double totalFlow; // 添加totalFlow属性

        public TimeSeriesData(LocalDateTime timeValue, double precipitation) {
            this.timeValue = timeValue;
            this.precipitation = precipitation;
        }
    }
}